//
// xorglib.cc
//

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <iostream>

#include "xorglib.h"

#define MAX_PROPERTY_VALUE_LEN 4096

using namespace std;

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo + UBUNTU PATCH for 64bit
static gchar  *get_property(Display *disp, Window win,
                           Atom xa_prop_type, const gchar *prop_name,
                           unsigned long *size)
{
    Atom           xa_prop_name;
    Atom           xa_ret_type;
    int            ret_format;
    unsigned long  ret_nitems;
    unsigned long  ret_bytes_after;
    unsigned long  tmp_size;
    unsigned char *ret_prop;
    gchar         *ret;
    
    xa_prop_name = XInternAtom(disp, prop_name, False);
    
    /* MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
     *
     * long_length = Specifies the length in 32-bit multiples of the
     *               data to be retrieved.
     *
     * NOTE:  see 
     * http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
     * In particular:
     *
     * 	When the X window system was ported to 64-bit architectures, a
     * rather peculiar design decision was made. 32-bit quantities such
     * as Window IDs, atoms, etc, were kept as longs in the client side
     * APIs, even when long was changed to 64 bits.
     *
     */
    if(XGetWindowProperty(disp, win, xa_prop_name, 0, MAX_PROPERTY_VALUE_LEN / 4, False,
                          xa_prop_type, &xa_ret_type, &ret_format,     
                          &ret_nitems, &ret_bytes_after, &ret_prop) != Success)
    {
        printf("Cannot get %s property.\n", prop_name);
        return NULL;
    }
    
    if(xa_ret_type != xa_prop_type)
    {
        printf("Invalid type of %s property.\n", prop_name);
        XFree(ret_prop);
        return NULL;
    }
    
    /* null terminate the result to make string handling easier */
    tmp_size = (ret_format / 8) * ret_nitems;
    
    /* UBUNTU PATCH Correct 64 Architecture implementation of 32 bit data */
    if(ret_format==32) tmp_size *= sizeof(long)/4;
    
    ret = (gchar *)g_malloc(tmp_size + 1);
    memcpy(ret, ret_prop, tmp_size);
    ret[tmp_size] = '\0';
    
    if(size)
    {
        *size = tmp_size;
    }
    
    XFree(ret_prop);
    return ret;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static gchar  *get_window_title(Display *disp, Window win)
{
    gchar *title_utf8;
    gchar *wm_name;
    gchar *net_wm_name;
    
    wm_name = get_property(disp, win, XA_STRING, "WM_NAME", NULL);
    net_wm_name = get_property(disp, win, XInternAtom(disp, "UTF8_STRING", False), "_NET_WM_NAME", NULL);
    
    if(net_wm_name)
    {
        title_utf8 = g_strdup(net_wm_name);
    }
    else
    {
        if(wm_name)
        {
            title_utf8 = g_locale_to_utf8(wm_name, -1, NULL, NULL, NULL);
        }
        else
        {
            title_utf8 = NULL;
        }
    }
    
    g_free(wm_name);
    g_free(net_wm_name);
    
    return title_utf8;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static gchar  *get_window_class(Display *disp, Window win)
{
    gchar *class_utf8;
    gchar *wm_class;
    unsigned long size;
    
    wm_class = get_property(disp, win, XA_STRING, "WM_CLASS", &size);
    if(wm_class)
    {
        gchar *p_0 = strchr(wm_class, '\0');
        if(wm_class + size - 1 > p_0)
        {
            *(p_0) = '.';
        }
        class_utf8 = g_locale_to_utf8(wm_class, -1, NULL, NULL, NULL);
    }
    else
    {
        class_utf8 = NULL;
    }
    
    g_free(wm_class);
    
    return class_utf8;
}

// from wmctrl 1.07 (http://tomas.styblo.name/wmctrl/) by Tomas Styblo
static Window  *get_client_list(Display *disp, unsigned long *size)
{
    Window *client_list = (Window *)get_property(disp, DefaultRootWindow(disp), XA_WINDOW, "_NET_CLIENT_LIST", size);
    if(client_list == NULL)
    {
        client_list = (Window *)get_property(disp, DefaultRootWindow(disp), XA_CARDINAL, "_WIN_CLIENT_LIST", size);
        if(client_list == NULL)
        {
            fputs("Cannot get client list properties. \n"
                  "(_NET_CLIENT_LIST or _WIN_CLIENT_LIST)"
                  "\n", stderr);
            return NULL;
        }
    }
    return client_list;
}

XOrgLib::XOrgLib() : mp_display(NULL)
{
    if(!(mp_display = XOpenDisplay(NULL)))
    {
        cout << "Cannot open display" << endl;
        mp_display = NULL;
        return;
    }
    int  event_base, error_base;
    int  major_version, minor_version;
    if(!XTestQueryExtension(mp_display,
                            &event_base,
                            &error_base,
                            &major_version,
                            &minor_version))
    {
        cout << "XTest extension not supported" << endl;
        XCloseDisplay(mp_display);
        mp_display = NULL;
        return;
    }
    //XTestGrabControl(mp_display, True);
}

XOrgLib::~XOrgLib()
{
    if(mp_display!= NULL)
    {
        XCloseDisplay(mp_display);
    }
}

Window  XOrgLib::get_destination_window()
{
    unsigned long  client_list_size;
    Window        *client_list = get_client_list(mp_display, &client_list_size);
    if(client_list == NULL)
    {
        return 0;
    }
    
    for(unsigned int  i = 0; i < client_list_size / sizeof(Window); i++)
    {
        /* desktop ID */
        long *p_desktop = (long *)get_property(mp_display, client_list[i], XA_CARDINAL, "_NET_WM_DESKTOP", NULL);
        if(p_desktop == NULL)
        {
            p_desktop = (long *)get_property(mp_display, client_list[i], XA_CARDINAL, "_WIN_WORKSPACE", NULL);
        }
        if(p_desktop && *p_desktop < 0)
        {
            g_free(p_desktop);
            continue;
        }
        
        /* class */
        gchar *class_out = get_window_class(mp_display, client_list[i]); /* UTF8 */
        if(strstr(class_out, "gedit") != NULL)
        {
            /* title */
            gchar *title_utf8 = get_window_title(mp_display, client_list[i]); /* UTF8 */
            
            /* pid */
            unsigned long *p_pid;
            p_pid = (unsigned long *)get_property(mp_display, client_list[i], XA_CARDINAL, "_NET_WM_PID", NULL);
            
            /* geometry */
            int           x, y, junkx, junky;
            unsigned int  wwidth, wheight, bw, depth;
            Window        junkroot;
            XGetGeometry(mp_display, client_list[i], &junkroot, &junkx, &junky, &wwidth, &wheight, &bw, &depth);
            XTranslateCoordinates(mp_display, client_list[i], junkroot, junkx, junky, &x, &y, &junkroot);
            
            /*
            cout << "cli=" << client_list[i]
                 << ", des=" << (p_desktop ? *p_desktop : 0)
                 << ", pid=" << (p_pid ? *p_pid : 0)
                 << ", geom=[" << x << ", " << y << ", " << wwidth << ", " << wheight << "]"
                 << ",\n\tclass=" << (class_out ? class_out : "N/A")
                 << ", title=" << (title_utf8 ? title_utf8 : "N/A") << endl;
            */
            
            g_free(p_desktop);
            g_free(class_out);
            g_free(title_utf8);
            g_free(p_pid);
            Window  ret_window = client_list[i];
            g_free(client_list);
            return ret_window;
        }
        g_free(p_desktop);
        g_free(class_out);
    }
    g_free(client_list);
    return 0;
}

void  XOrgLib::send_arrow_none_event()
{
    XSync(mp_display, 0);
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Up), 0, 0); XFlush(mp_display);
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Down), 0, 0); XFlush(mp_display);
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Left), 0, 0); XFlush(mp_display);
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Right), 0, 0); XFlush(mp_display);
}

void  XOrgLib::send_arrow_up_event()
{
    send_arrow_none_event();
    
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Up), 1, 0); XFlush(mp_display);
    //XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Right), 1, 0); XFlush(mp_display);
    
    send_arrow_none_event();
}

void  XOrgLib::send_arrow_down_event()
{
    send_arrow_none_event();
    
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Down), 1, 0); XFlush(mp_display);
    //XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Left), 1, 0); XFlush(mp_display);
    
    send_arrow_none_event();
}

void  XOrgLib::send_arrow_left_event()
{
    send_arrow_none_event();
    
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Left), 1, 0); XFlush(mp_display);
    //XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Right), 1, 0); XFlush(mp_display);
    
    send_arrow_none_event();
}

void  XOrgLib::send_arrow_right_event()
{
    send_arrow_none_event();
    
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Right), 1, 0); XFlush(mp_display);
    //XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Left), 1, 0); XFlush(mp_display);
    
    send_arrow_none_event();
}

void  XOrgLib::send_key_event_from_string(const char *key_str)
{
    Window  dest_window = get_destination_window();
    if(dest_window)
    {
        XSetInputFocus(mp_display, dest_window, RevertToPointerRoot, CurrentTime);
        XSync(mp_display, 1);
        XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XStringToKeysym(key_str)), 1, 0);
        XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XStringToKeysym(key_str)), 0, 0);
        XFlush(mp_display);
    }
    else
        cout << "ERROR: failed to retrieve destination window" << endl;
}

void  XOrgLib::send_key(KeySym keysym, uchar modflags)
{
    XTestGrabControl(mp_display, 1);
    
    /* Generate modkey press */
    if(modflags)
    {
        if(modflags & FLAG_CTRL) XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Control_L), 1, 0);
        if(modflags & FLAG_ALT) XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Alt_L), 1, 0);
        if(modflags & FLAG_SHIFT) XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Shift_L), 1, 0);
        else if(modflags & FLAG_CAPSLOCK)
            XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Caps_Lock), 1, 0);
    }
    
    /* Generate regular key press and release */
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, keysym), 1, 0);
    XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, keysym), 0, 0);
    
    /* Generate modkey release */
    if(modflags)
    {
        if(modflags & FLAG_CTRL) XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Control_L), 0, 0);
        if(modflags & FLAG_ALT) XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Alt_L), 0, 0);
        if(modflags & FLAG_SHIFT) XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Shift_L), 0, 0);
        else if(modflags & FLAG_CAPSLOCK)
            XTestFakeKeyEvent(mp_display, XKeysymToKeycode(mp_display, XK_Caps_Lock), 0, 0);
    }
    
    XSync(mp_display, 0);
    XTestGrabControl(mp_display, 0);
}
